using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace WyUnity.Utility.Dynamic
{
	public delegate void SetValueDelegateHandler(object target, object value);
	
	public static class DynamicCall
	{
		[DebuggerHidden]
		public static Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
		{
			// parameters to execute
			ParameterExpression instanceParameter =
				Expression.Parameter(typeof(object), "instance");
			ParameterExpression parametersParameter =
				Expression.Parameter(typeof(object[]), "parameters");

			// build parameter list
			List<Expression> parameterExpressions = new List<Expression>();
			ParameterInfo[] paramInfos = methodInfo.GetParameters();
			for (int i = 0; i < paramInfos.Length; i++)
			{
				// (Ti)parameters[i]
				BinaryExpression valueObj = Expression.ArrayIndex(
					parametersParameter, Expression.Constant(i));
				UnaryExpression valueCast = Expression.Convert(
					valueObj, paramInfos[i].ParameterType);

				parameterExpressions.Add(valueCast);
			}

			// non-instance for static method, or ((TInstance)instance)
			Expression instanceCast = methodInfo.IsStatic ? null :
																	Expression.Convert(instanceParameter, methodInfo.ReflectedType);

			// static invoke or ((TInstance)instance).Method
			MethodCallExpression methodCall = Expression.Call(
				instanceCast, methodInfo, parameterExpressions);

			// ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
			if (methodCall.Type == typeof(void))
			{
				Expression<Action<object, object[]>> lambda =
					Expression.Lambda<Action<object, object[]>>(
						methodCall, instanceParameter, parametersParameter);

				Action<object, object[]> execute = lambda.Compile();
				return (instance, parameters) =>
						{
							execute(instance, parameters);
							return null;
						};
			}
			else
			{
				UnaryExpression castMethodCall = Expression.Convert(
					methodCall, typeof(object));
				Expression<Func<object, object[], object>> lambda =
					Expression.Lambda<Func<object, object[], object>>(
						castMethodCall, instanceParameter, parametersParameter);

				return lambda.Compile();
			}
		}

		public static MethodInfo GetPropertySetMethodInfo(Type type, string prop)
		{
			return type.GetProperty(prop, BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty).GetSetMethod();
		}

#if DEBUG
		public static void DisplayAllMethod<T>()
		{
			var t = typeof(T);
			var ms = t.GetMethods(BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetField | BindingFlags.SetProperty);
			ms.ToList().ForEach(m =>
									{
										Debug.Write(m.IsPublic ? "public " : "private ");
										Debug.Write(m.ReturnType.Name + " ");
										Debug.Write(m.Name);
										Debug.Write("(...)");

										Debug.WriteLine(Environment.NewLine);
									});

		}
#endif
	}
}